﻿using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Trentacular.SharePoint.StsAdmin.Util;
using Trentacular.SharePoint.WSPUtil;

namespace Trentacular.SharePoint.StsAdmin
{
    public class FeatureActivator : BaseStsadmHelper
    {
        protected SPWebService FarmWebService
        {
            get { return SPFarm.Local.Services.GetValue<SPWebService>(); }
        }

        public DeploymentInventory DeploymentInventory { get; private set; }

        public FeatureActivator(DeploymentInventory inventory)
        {
            DeploymentInventory = inventory;
        }

        public void DeactivateAllInventoriedFeatures()
        {
            foreach (var deployInfo in DeploymentInventory.Values)
            {
                var farmFeatureActivations = deployInfo.FeatureActivations;

                foreach (var webAppId in farmFeatureActivations.Children.Keys)
                {
                    var webAppFeatureActivations = farmFeatureActivations.Children[webAppId];
                    var webApp = SPUtil.GetWebApplication(webAppId);

                    foreach (var siteId in webAppFeatureActivations.Children.Keys)
                    {
                        var siteFeatureActivations = webAppFeatureActivations.Children[siteId];
                        using (var unprivelegedSite = new SPSite(siteId))
                        {
                            using (var site = (unprivelegedSite.SystemAccount == null) ? unprivelegedSite : new SPSite(unprivelegedSite.ID, unprivelegedSite.SystemAccount.UserToken))
                            {
                                foreach (var webId in siteFeatureActivations.Children.Keys)
                                {
                                    var webFeatureActivations = siteFeatureActivations.Children[webId];
                                    using (var web = site.OpenWeb(webId))
                                    {
                                        // Deactivate the web scoped features
                                        foreach (var webFeatureDefinition in webFeatureActivations.ActivatedFeatures)
                                        {
                                            EnsureWebFeatureDeactivated(web, webFeatureDefinition);
                                        }
                                    }
                                }

                                // Deactivate the site scoped features
                                foreach (var siteFeatureId in siteFeatureActivations.ActivatedFeatures)
                                {
                                    EnsureSiteFeatureDeactivated(site, siteFeatureId);
                                }
                            }
                        }
                    }

                    // Deactivate the web application scoped features
                    foreach (var webAppFeatureId in webAppFeatureActivations.ActivatedFeatures)
                    {
                        EnsureWebApplicationFeatureDeactivated(webApp, webAppFeatureId);
                    }
                }

                // Deactivate the farm scoped features
                foreach (var farmFeatureId in farmFeatureActivations.ActivatedFeatures)
                {
                    EnsureFarmFeatureDeactivated(farmFeatureId);
                }
            }
        }

        public void RestoreInventoriedFeatureActivations()
        {
            foreach (var deployInfo in DeploymentInventory.Values)
            {
                var farmFeatureActivations = deployInfo.FeatureActivations;

                // Activate the farm scoped features
                foreach (var farmFeatureDefinition in farmFeatureActivations.ActivatedFeatures)
                {
                    EnsureFarmFeatureActivated(farmFeatureDefinition);
                }

                foreach (var webAppId in farmFeatureActivations.Children.Keys)
                {
                    var webAppFeatureActivations = farmFeatureActivations.Children[webAppId];
                    var webApp = SPUtil.GetWebApplication(webAppId);

                    // Activate the web application scoped features
                    foreach (var webAppFeatureDefinition in webAppFeatureActivations.ActivatedFeatures)
                    {
                        EnsureWebApplicationFeatureActivated(webApp, webAppFeatureDefinition);
                    }

                    foreach (var siteId in webAppFeatureActivations.Children.Keys)
                    {
                        var siteFeatureActivations = webAppFeatureActivations.Children[siteId];
                        using (var unprivelegedSite = new SPSite(siteId))
                        {
                            using (var site = (unprivelegedSite.SystemAccount == null) ? unprivelegedSite : new SPSite(unprivelegedSite.ID, unprivelegedSite.SystemAccount.UserToken))
                            {
                                // Activate the site scoped features
                                foreach (var siteFeatureDefinition in siteFeatureActivations.ActivatedFeatures)
                                {
                                    EnsureSiteFeatureActivated(site, siteFeatureDefinition);
                                }

                                foreach (var webId in siteFeatureActivations.Children.Keys)
                                {
                                    var webFeatureActivations = siteFeatureActivations.Children[webId];
                                    using (var web = site.OpenWeb(webId))
                                    {
                                        // Activate the web scoped features
                                        foreach (var webFeatureDefinition in webFeatureActivations.ActivatedFeatures)
                                        {
                                            EnsureWebFeatureActivated(web, webFeatureDefinition);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public void EnsureFarmFeatureDeactivated(WSPFeatureDefinition featureDefinition)
        {
            var dependentFeatures = DeploymentInventory.GetDependentFeatures(featureDefinition.Id);
            foreach (var dependentFeature in dependentFeatures)
            {
                if (dependentFeature.Scope != SPFeatureScope.Farm)
                    continue;

                EnsureFarmFeatureDeactivated(dependentFeature);
            }

            //SPFarm.Local.FeatureDefinitions[featureId];
            DoConsoleAction(string.Format("Deactivating farm feature {0}", featureDefinition.Name), delegate()
            {
                FarmWebService.Features.EnsureDeactivated(featureDefinition.Id);
            });

            ExecuteJobsAndCleanupFailures();
        }

        public void EnsureWebApplicationFeatureDeactivated(SPWebApplication webApplication, WSPFeatureDefinition featureDefinition)
        {
            var dependentFeatures = DeploymentInventory.GetDependentFeatures(featureDefinition.Id);
            foreach (var dependentFeature in dependentFeatures)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureDeactivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureDeactivated(webApplication, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Deactivating web application feature {0} at {1}", featureDefinition.Name, webApplication.GetUrl()), delegate()
            {
                webApplication.Features.EnsureDeactivated(featureDefinition.Id);
            });

            ExecuteJobsAndCleanupFailures();
        }

        public void EnsureSiteFeatureDeactivated(SPSite site, WSPFeatureDefinition featureDefinition)
        {
            var dependentFeatures = DeploymentInventory.GetDependentFeatures(featureDefinition.Id);
            foreach (var dependentFeature in dependentFeatures)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureDeactivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureDeactivated(site.WebApplication, dependentFeature);
                        break;
                    case SPFeatureScope.Site:
                        EnsureSiteFeatureDeactivated(site, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Deactivating site feature {0} at {1}", featureDefinition.Name, site.Url), delegate()
            {
                site.Features.EnsureDeactivated(featureDefinition.Id);
            });

            ExecuteJobsAndCleanupFailures();
        }

        public void EnsureWebFeatureDeactivated(SPWeb web, WSPFeatureDefinition featureDefinition)
        {
            var dependentFeatures = DeploymentInventory.GetDependentFeatures(featureDefinition.Id);
            foreach (var dependentFeature in dependentFeatures)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureDeactivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureDeactivated(web.Site.WebApplication, dependentFeature);
                        break;
                    case SPFeatureScope.Site:
                        EnsureSiteFeatureDeactivated(web.Site, dependentFeature);
                        break;
                    case SPFeatureScope.Web:
                        EnsureWebFeatureDeactivated(web, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Deactivating web feature {0} at {1}", featureDefinition.Name, web.Url), delegate()
            {
                web.Features.EnsureDeactivated(featureDefinition.Id);
            });

            ExecuteJobsAndCleanupFailures();
        }

        private bool DoesFeatureExist(WSPFeatureDefinition featureDefinition)
        {
            // Ensure feature exists in feature store
            if (SPFarm.Local.FeatureDefinitions.Exists(featureDefinition.Id))
                return true;

            Console.WriteLine(string.Format("Skipping feature activation since feature definition no longer exists: {0}", featureDefinition.Name));

            return false;
        }



        public void EnsureFarmFeatureActivated(WSPFeatureDefinition featureDefinition)
        {
            if (!DoesFeatureExist(featureDefinition))
                return;

            var activationDependencies = DeploymentInventory.GetInventoriedActivationDependencies(featureDefinition);
            foreach (var dependentFeature in activationDependencies)
            {
                if (dependentFeature.Scope != SPFeatureScope.Farm)
                    continue;

                EnsureFarmFeatureActivated(dependentFeature);
            }

            DoConsoleAction(string.Format("Activating farm feature {0}", featureDefinition.Name), delegate()
            {
                FarmWebService.Features.EnsureActivated(featureDefinition.Id);
            });

            JobDefinitionExecuter.ExecuteJobDefinitions();
        }

        public void EnsureWebApplicationFeatureActivated(SPWebApplication webApplication, WSPFeatureDefinition featureDefinition)
        {
            if (!DoesFeatureExist(featureDefinition))
                return;

            var activationDependencies = DeploymentInventory.GetInventoriedActivationDependencies(featureDefinition);
            foreach (var dependentFeature in activationDependencies)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureActivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureActivated(webApplication, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Activating web application feature {0} at {1}", featureDefinition.Name, webApplication.GetUrl()), delegate()
            {
                webApplication.Features.EnsureActivated(featureDefinition.Id);
            });

            JobDefinitionExecuter.ExecuteJobDefinitions();
        }

        public void EnsureSiteFeatureActivated(SPSite site, WSPFeatureDefinition featureDefinition)
        {
            if (!DoesFeatureExist(featureDefinition))
                return;

            var activationDependencies = DeploymentInventory.GetInventoriedActivationDependencies(featureDefinition);
            foreach (var dependentFeature in activationDependencies)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureActivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureActivated(site.WebApplication, dependentFeature);
                        break;
                    case SPFeatureScope.Site:
                        EnsureSiteFeatureActivated(site, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Activating site feature {0} at {1}", featureDefinition.Name, site.Url), delegate()
            {
                site.Features.EnsureActivated(featureDefinition.Id);
            });

            JobDefinitionExecuter.ExecuteJobDefinitions();
        }

        public void EnsureWebFeatureActivated(SPWeb web, WSPFeatureDefinition featureDefinition)
        {
            if (!DoesFeatureExist(featureDefinition))
                return;

            var activationDependencies = DeploymentInventory.GetInventoriedActivationDependencies(featureDefinition);
            foreach (var dependentFeature in activationDependencies)
            {
                switch (dependentFeature.Scope)
                {
                    case SPFeatureScope.Farm:
                        EnsureFarmFeatureActivated(dependentFeature);
                        break;
                    case SPFeatureScope.WebApplication:
                        EnsureWebApplicationFeatureActivated(web.Site.WebApplication, dependentFeature);
                        break;
                    case SPFeatureScope.Site:
                        EnsureSiteFeatureActivated(web.Site, dependentFeature);
                        break;
                    case SPFeatureScope.Web:
                        EnsureWebFeatureActivated(web, dependentFeature);
                        break;
                }
            }

            DoConsoleAction(string.Format("Activating web feature {0} at {1}", featureDefinition.Name, web.Url), delegate()
            {
                web.Features.EnsureActivated(featureDefinition.Id);
            });

            JobDefinitionExecuter.ExecuteJobDefinitions();
        }
    }
}
